前言
call 、apply 、bind 是 Function 对象自带的三个方法,可以改变函数体内部 this 的指向。三者第一个参数都是this要指向的对象,非严格模式下,如果没有这个参数或参数为undefined或null,则默认指向全局window。
call()
使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法。
call
的用处简而言之就是可以让 call() 中的对象调用当前对象所拥有的function,比如Object.prototype.toString.call([])
,就是 call 中的 Array
对象调用了当前 Object
对象上的方法。
语法
fun.call(thisArg[, arg1[, arg2[, ...]]])
thisArg
在fun函数运行时指定的this
值。需要注意的是下面几种情况
- 不传,或者传
null
,undefined
, 函数中的this
指向window对象 - 传递另一个函数的函数名,函数中的
this
指向这个函数的引用,并不一定是该函数执行时真正的this
值 - 值为原始值(数字,字符串,布尔值)的
this
会指向该原始值的自动包装对象,如String
、Number
、Boolean
- 传递一个对象,函数中的this指向这个对象
arg1, arg2, …
指定的参数列表。
1 | function a(){ |
apply()
语法与 call()
方法的语法几乎完全相同,唯一的区别在于,apply的第二个参数必须是一个包含多个参数的数组(或类数组对象)。
语法:
fun.apply(thisArg[, argsArray])
注意: 需要注意:Chrome 14
以及 Internet Explorer 9
仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。
thisArg
同上call
的thisArg
参数。
argsArray
一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun
函数。如果该参数的值为null
或 undefined
,则表示不需要传入任何参数。从ECMAScript 5
开始可以使用类数组对象。
call、apply 区别
- call 接收的是参数列表
- apply 接收的是参数数组
1 | var a ={ |
如果传入一个原始值(字符串、布尔或数字类型)来当做
this
的绑定对象, 这个原始值会被转换成它的对象形式(new String()
),这通常被称为“装箱”。
call、apply 使用场景
1. 根据自己的需要灵活修改this指向
1 | var foo = { |
2. 将类数组对象转换为数组
1 | function exam(a, b, c, d, e) { |
3. 实现继承
1 | // 定义父级的构造函数 |
在Student的构造函数中,借助call方法,将父级的构造函数执行了一次,相当于将Person中的代码,在Sudent中复制了一份,其中的this指向为从Student中new出来的实例对象。call方法保证了this的指向正确,因此就相当于实现了继承。Student的构造函数等同于下。
1 | var Student = function (name, age, high) { |
4. 在向其他执行上下文的传递中,确保this的指向保持不变
如下面的例子中,我们期待的是getA被obj调用时,this指向obj,但是由于匿名函数的存在导致了this指向的丢失,在这个匿名函数中this指向了全局,因此我们需要想一些办法找回正确的this指向。
1 | var obj = { |
常规的解决办法很简单,就是使用一个变量,将this的引用保存起来。我们常常会用到这方法,但是我们也要借助上面讲到过的知识,来判断this是否在传递中被修改了,如果没有被修改,就没有必要这样使用了。
1 | var obj = { |
另外就是借助闭包与apply方法,封装一个bind方法。
1 | function bind(fn, obj) { |
当然,也可以使用ES5中已经自带的bind方法。它与我上面封装的bind方法是一样的效果。
1 | var obj = { |
ES6中也常常使用箭头函数的方式来替代这种方案
bind()
bind() 函数会创建一个新函数(称为绑定函数)
bind是ES5新增的一个方法
传参和call或apply类似
不会执行对应的函数,call或apply会自动执行对应的函数
返回对函数的引用
语法
fun.bind(thisArg[, arg1[, arg2[, ...]]])
下面例子:当点击网页时,EventClick
被触发执行,输出JSLite.io p1 p2
, 说明EventClick
中的this
被bind
改变成了obj
对象。如果你将EventClick.bind(obj,'p1','p2')
变成 EventClick.call(obj,'p1','p2')
的话,页面会直接输出 JSLite.io p1 p2
1 | var obj = {name:'JSLite.io'}; |
call、apply 与 bind 区别
bind
是返回绑定this之后的函数,apply
、call
则是立即执行- 三者都可以传参,且
apply
和call
是一次性传入参数,而bind
可以分为多次传入
1 | var a ={ |
多次绑定 bind 只有第一次是有效的
1 | var one = function(){ |
因为 bind() 的实现,相当于使用函数在内部包了一个 call/apply,第二次 bind() 相当于再包住第一次 bind(),所以后面的 bind 都是无效的
1 | var fn = function() { |
利用 call() 和 apply() 方法实现 bind() 方法
1 | // 未兼容 new 版 |